﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information
// 
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
// 
// http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 

// .NET Compact Framework 1.0 has no support for reading assembly attributes
// and uses the CompactRepositorySelector instead
#if !NETCF

using System;
using System.Collections;
#if !NETSTANDARD
using System.Configuration;
#else
using System.Linq;
#endif
using System.IO;
using System.Reflection;

using log4net.Config;
using log4net.Util;
using log4net.Repository;

namespace log4net.Core
{
    /// <summary>
    /// The default implementation of the <see cref="IRepositorySelector"/> interface.
    /// </summary>
    /// <remarks>
    /// <para>
    /// Uses attributes defined on the calling assembly to determine how to
    /// configure the hierarchy for the repository.
    /// </para>
    /// </remarks>
    /// <author>Nicko Cadell</author>
    /// <author>Gert Driesen</author>
    public class DefaultRepositorySelector : IRepositorySelector
    {
        /// <summary>
        /// Event to notify that a logger repository has been created.
        /// </summary>
        /// <value>
        /// Event to notify that a logger repository has been created.
        /// </value>
        /// <remarks>
        /// <para>
        /// Event raised when a new repository is created.
        /// The event source will be this selector. The event args will
        /// be a <see cref="LoggerRepositoryCreationEventArgs"/> which
        /// holds the newly created <see cref="ILoggerRepository"/>.
        /// </para>
        /// </remarks>
        public event LoggerRepositoryCreationEventHandler LoggerRepositoryCreatedEvent 
        {
            add { this.m_loggerRepositoryCreatedEvent += value; }
            remove { this.m_loggerRepositoryCreatedEvent -= value; }
        }

        /// <summary>
        /// Creates a new repository selector.
        /// </summary>
        /// <param name="defaultRepositoryType">The type of the repositories to create, must implement <see cref="ILoggerRepository"/></param>
        /// <remarks>
        /// <para>
        /// Create an new repository selector.
        /// The default type for repositories must be specified,
        /// an appropriate value would be <see cref="log4net.Repository.Hierarchy.Hierarchy"/>.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="defaultRepositoryType"/> is <see langword="null" />.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="defaultRepositoryType"/> does not implement <see cref="ILoggerRepository"/>.</exception>
        public DefaultRepositorySelector(Type defaultRepositoryType)
        {
            if (defaultRepositoryType == null)
            {
                throw new ArgumentNullException("defaultRepositoryType");
            }

            // Check that the type is a repository
            if (!(typeof(ILoggerRepository).IsAssignableFrom(defaultRepositoryType)))
            {
                throw SystemInfo.CreateArgumentOutOfRangeException("defaultRepositoryType", defaultRepositoryType, "Parameter: defaultRepositoryType, Value: [" + defaultRepositoryType + "] out of range. Argument must implement the ILoggerRepository interface");
            }

            this.m_defaultRepositoryType = defaultRepositoryType;

            LogLog.Debug(declaringType, "defaultRepositoryType [" + this.m_defaultRepositoryType + "]");
        }

        /// <summary>
        /// Gets the <see cref="ILoggerRepository"/> for the specified assembly.
        /// </summary>
        /// <param name="repositoryAssembly">The assembly use to lookup the <see cref="ILoggerRepository"/>.</param>
        /// <remarks>
        /// <para>
        /// The type of the <see cref="ILoggerRepository"/> created and the repository 
        /// to create can be overridden by specifying the <see cref="log4net.Config.RepositoryAttribute"/> 
        /// attribute on the <paramref name="repositoryAssembly"/>.
        /// </para>
        /// <para>
        /// The default values are to use the <see cref="log4net.Repository.Hierarchy.Hierarchy"/> 
        /// implementation of the <see cref="ILoggerRepository"/> interface and to use the
        /// <see cref="AssemblyName.Name"/> as the name of the repository.
        /// </para>
        /// <para>
        /// The <see cref="ILoggerRepository"/> created will be automatically configured using 
        /// any <see cref="log4net.Config.ConfiguratorAttribute"/> attributes defined on
        /// the <paramref name="repositoryAssembly"/>.
        /// </para>
        /// </remarks>
        /// <returns>The <see cref="ILoggerRepository"/> for the assembly</returns>
        /// <exception cref="ArgumentNullException"><paramref name="repositoryAssembly"/> is <see langword="null" />.</exception>
        public ILoggerRepository GetRepository(Assembly repositoryAssembly)
        {
            if (repositoryAssembly == null)
            {
                throw new ArgumentNullException("repositoryAssembly");
            }
            return this.CreateRepository(repositoryAssembly, this.m_defaultRepositoryType);
        }

        /// <summary>
        /// Gets the <see cref="ILoggerRepository"/> for the specified repository.
        /// </summary>
        /// <param name="repositoryName">The repository to use to lookup the <see cref="ILoggerRepository"/>.</param>
        /// <returns>The <see cref="ILoggerRepository"/> for the specified repository.</returns>
        /// <remarks>
        /// <para>
        /// Returns the named repository. If <paramref name="repositoryName"/> is <c>null</c>
        /// a <see cref="ArgumentNullException"/> is thrown. If the repository 
        /// does not exist a <see cref="LogException"/> is thrown.
        /// </para>
        /// <para>
        /// Use <see cref="M:CreateRepository(string, Type)"/> to create a repository.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="repositoryName"/> is <see langword="null" />.</exception>
        /// <exception cref="LogException"><paramref name="repositoryName"/> does not exist.</exception>
        public ILoggerRepository GetRepository(string repositoryName)
        {
            if (repositoryName == null)
            {
                throw new ArgumentNullException("repositoryName");
            }

            lock(this)
            {
                // Lookup in map
                ILoggerRepository rep = this.m_name2repositoryMap[repositoryName] as ILoggerRepository;
                if (rep == null)
                {
                    throw new LogException("Repository [" + repositoryName + "] is NOT defined.");
                }
                return rep;
            }
        }

        /// <summary>
        /// Create a new repository for the assembly specified 
        /// </summary>
        /// <param name="repositoryAssembly">the assembly to use to create the repository to associate with the <see cref="ILoggerRepository"/>.</param>
        /// <param name="repositoryType">The type of repository to create, must implement <see cref="ILoggerRepository"/>.</param>
        /// <returns>The repository created.</returns>
        /// <remarks>
        /// <para>
        /// The <see cref="ILoggerRepository"/> created will be associated with the repository
        /// specified such that a call to <see cref="M:GetRepository(Assembly)"/> with the
        /// same assembly specified will return the same repository instance.
        /// </para>
        /// <para>
        /// The type of the <see cref="ILoggerRepository"/> created and
        /// the repository to create can be overridden by specifying the
        /// <see cref="log4net.Config.RepositoryAttribute"/> attribute on the 
        /// <paramref name="repositoryAssembly"/>.  The default values are to use the 
        /// <paramref name="repositoryType"/> implementation of the 
        /// <see cref="ILoggerRepository"/> interface and to use the
        /// <see cref="AssemblyName.Name"/> as the name of the repository.
        /// </para>
        /// <para>
        /// The <see cref="ILoggerRepository"/> created will be automatically
        /// configured using any <see cref="log4net.Config.ConfiguratorAttribute"/> 
        /// attributes defined on the <paramref name="repositoryAssembly"/>.
        /// </para>
        /// <para>
        /// If a repository for the <paramref name="repositoryAssembly"/> already exists
        /// that repository will be returned. An error will not be raised and that 
        /// repository may be of a different type to that specified in <paramref name="repositoryType"/>.
        /// Also the <see cref="log4net.Config.RepositoryAttribute"/> attribute on the
        /// assembly may be used to override the repository type specified in 
        /// <paramref name="repositoryType"/>.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="repositoryAssembly"/> is <see langword="null" />.</exception>
        public ILoggerRepository CreateRepository(Assembly repositoryAssembly, Type repositoryType)
        {
            return this.CreateRepository(repositoryAssembly, repositoryType, DefaultRepositoryName, true);
        }

        /// <summary>
        /// Creates a new repository for the assembly specified.
        /// </summary>
        /// <param name="repositoryAssembly">the assembly to use to create the repository to associate with the <see cref="ILoggerRepository"/>.</param>
        /// <param name="repositoryType">The type of repository to create, must implement <see cref="ILoggerRepository"/>.</param>
        /// <param name="repositoryName">The name to assign to the created repository</param>
        /// <param name="readAssemblyAttributes">Set to <c>true</c> to read and apply the assembly attributes</param>
        /// <returns>The repository created.</returns>
        /// <remarks>
        /// <para>
        /// The <see cref="ILoggerRepository"/> created will be associated with the repository
        /// specified such that a call to <see cref="M:GetRepository(Assembly)"/> with the
        /// same assembly specified will return the same repository instance.
        /// </para>
        /// <para>
        /// The type of the <see cref="ILoggerRepository"/> created and
        /// the repository to create can be overridden by specifying the
        /// <see cref="log4net.Config.RepositoryAttribute"/> attribute on the 
        /// <paramref name="repositoryAssembly"/>.  The default values are to use the 
        /// <paramref name="repositoryType"/> implementation of the 
        /// <see cref="ILoggerRepository"/> interface and to use the
        /// <see cref="AssemblyName.Name"/> as the name of the repository.
        /// </para>
        /// <para>
        /// The <see cref="ILoggerRepository"/> created will be automatically
        /// configured using any <see cref="log4net.Config.ConfiguratorAttribute"/> 
        /// attributes defined on the <paramref name="repositoryAssembly"/>.
        /// </para>
        /// <para>
        /// If a repository for the <paramref name="repositoryAssembly"/> already exists
        /// that repository will be returned. An error will not be raised and that 
        /// repository may be of a different type to that specified in <paramref name="repositoryType"/>.
        /// Also the <see cref="log4net.Config.RepositoryAttribute"/> attribute on the
        /// assembly may be used to override the repository type specified in 
        /// <paramref name="repositoryType"/>.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="repositoryAssembly"/> is <see langword="null" />.</exception>
        public ILoggerRepository CreateRepository(Assembly repositoryAssembly, Type repositoryType, string repositoryName, bool readAssemblyAttributes)
        {
            if (repositoryAssembly == null)
            {
                throw new ArgumentNullException("repositoryAssembly");
            }

            // If the type is not set then use the default type
            if (repositoryType == null)
            {
                repositoryType = this.m_defaultRepositoryType;
            }

            lock(this)
            {
                // Lookup in map
                ILoggerRepository rep = this.m_assembly2repositoryMap[repositoryAssembly] as ILoggerRepository;
                if (rep == null)
                {
                    // Not found, therefore create
                    LogLog.Debug(declaringType, "Creating repository for assembly [" + repositoryAssembly + "]");

                    // Must specify defaults
                    string actualRepositoryName = repositoryName;
                    Type actualRepositoryType = repositoryType;

                    if (readAssemblyAttributes)
                    {
                        // Get the repository and type from the assembly attributes
                        this.GetInfoForAssembly(repositoryAssembly, ref actualRepositoryName, ref actualRepositoryType);
                    }

                    LogLog.Debug(declaringType, "Assembly [" + repositoryAssembly + "] using repository [" + actualRepositoryName + "] and repository type [" + actualRepositoryType + "]");

                    // Lookup the repository in the map (as this may already be defined)
                    rep = this.m_name2repositoryMap[actualRepositoryName] as ILoggerRepository;
                    if (rep == null)
                    {
                        // Create the repository
                        rep = this.CreateRepository(actualRepositoryName, actualRepositoryType);

                        if (readAssemblyAttributes)
                        {
                            try
                            {
                                // Look for aliasing attributes
                                this.LoadAliases(repositoryAssembly, rep);

                                // Look for plugins defined on the assembly
                                this.LoadPlugins(repositoryAssembly, rep);

                                // Configure the repository using the assembly attributes
                                this.ConfigureRepository(repositoryAssembly, rep);
                            }
                            catch (Exception ex)
                            {
                                LogLog.Error(declaringType, "Failed to configure repository [" + actualRepositoryName + "] from assembly attributes.", ex);
                            }
                        }
                    }
                    else
                    {
                        LogLog.Debug(declaringType, "repository [" + actualRepositoryName + "] already exists, using repository type [" + rep.GetType().FullName + "]");

                        if (readAssemblyAttributes)
                        {
                            try
                            {
                                // Look for plugins defined on the assembly
                                this.LoadPlugins(repositoryAssembly, rep);
                            }
                            catch (Exception ex)
                            {
                                LogLog.Error(declaringType, "Failed to configure repository [" + actualRepositoryName + "] from assembly attributes.", ex);
                            }
                        }
                    }

                    this.m_assembly2repositoryMap[repositoryAssembly] = rep;
                }
                return rep;
            }
        }

        /// <summary>
        /// Creates a new repository for the specified repository.
        /// </summary>
        /// <param name="repositoryName">The repository to associate with the <see cref="ILoggerRepository"/>.</param>
        /// <param name="repositoryType">The type of repository to create, must implement <see cref="ILoggerRepository"/>.
        /// If this param is <see langword="null" /> then the default repository type is used.</param>
        /// <returns>The new repository.</returns>
        /// <remarks>
        /// <para>
        /// The <see cref="ILoggerRepository"/> created will be associated with the repository
        /// specified such that a call to <see cref="M:GetRepository(string)"/> with the
        /// same repository specified will return the same repository instance.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException"><paramref name="repositoryName"/> is <see langword="null" />.</exception>
        /// <exception cref="LogException"><paramref name="repositoryName"/> already exists.</exception>
        public ILoggerRepository CreateRepository(string repositoryName, Type repositoryType)
        {
            if (repositoryName == null)
            {
                throw new ArgumentNullException("repositoryName");
            }

            // If the type is not set then use the default type
            if (repositoryType == null)
            {
                repositoryType = this.m_defaultRepositoryType;
            }

            lock(this)
            {
                ILoggerRepository rep = null;

                // First check that the repository does not exist
                rep = this.m_name2repositoryMap[repositoryName] as ILoggerRepository;
                if (rep != null)
                {
                    throw new LogException("Repository [" + repositoryName + "] is already defined. Repositories cannot be redefined.");
                }
                else
                {
                    // Lookup an alias before trying to create the new repository
                    ILoggerRepository aliasedRepository = this.m_alias2repositoryMap[repositoryName] as ILoggerRepository;
                    if (aliasedRepository != null)
                    {
                        // Found an alias

                        // Check repository type
                        if (aliasedRepository.GetType() == repositoryType)
                        {
                            // Repository type is compatible
                            LogLog.Debug(declaringType, "Aliasing repository [" + repositoryName + "] to existing repository [" + aliasedRepository.Name + "]");
                            rep = aliasedRepository;

                            // Store in map
                            this.m_name2repositoryMap[repositoryName] = rep;
                        }
                        else
                        {
                            // Invalid repository type for alias
                            LogLog.Error(declaringType, "Failed to alias repository [" + repositoryName + "] to existing repository [" + aliasedRepository.Name + "]. Requested repository type [" + repositoryType.FullName + "] is not compatible with existing type [" + aliasedRepository.GetType().FullName + "]");

                            // We now drop through to create the repository without aliasing
                        }
                    }

                    // If we could not find an alias
                    if (rep == null)
                    {
                        LogLog.Debug(declaringType, "Creating repository [" + repositoryName + "] using type [" + repositoryType + "]");

                        // Call the no arg constructor for the repositoryType
                        rep = (ILoggerRepository)Activator.CreateInstance(repositoryType);

                        // Set the name of the repository
                        rep.Name = repositoryName;

                        // Store in map
                        this.m_name2repositoryMap[repositoryName] = rep;

                        // Notify listeners that the repository has been created
                        this.OnLoggerRepositoryCreatedEvent(rep);
                    }
                }

                return rep;
            }
        }

        /// <summary>
        /// Test if a named repository exists
        /// </summary>
        /// <param name="repositoryName">the named repository to check</param>
        /// <returns><c>true</c> if the repository exists</returns>
        /// <remarks>
        /// <para>
        /// Test if a named repository exists. Use <see cref="M:CreateRepository(string, Type)"/>
        /// to create a new repository and <see cref="M:GetRepository(string)"/> to retrieve 
        /// a repository.
        /// </para>
        /// </remarks>
        public bool ExistsRepository(string repositoryName)
        {
            lock(this)
            {
                return this.m_name2repositoryMap.ContainsKey(repositoryName);
            }
        }

        /// <summary>
        /// Gets a list of <see cref="ILoggerRepository"/> objects
        /// </summary>
        /// <returns>an array of all known <see cref="ILoggerRepository"/> objects</returns>
        /// <remarks>
        /// <para>
        /// Gets an array of all of the repositories created by this selector.
        /// </para>
        /// </remarks>
        public ILoggerRepository[] GetAllRepositories()
        {
            lock(this)
            {
                ICollection reps = this.m_name2repositoryMap.Values;
                ILoggerRepository[] all = new ILoggerRepository[reps.Count];
                reps.CopyTo(all, 0);
                return all;
            }
        }

        /// <summary>
        /// Aliases a repository to an existing repository.
        /// </summary>
        /// <param name="repositoryAlias">The repository to alias.</param>
        /// <param name="repositoryTarget">The repository that the repository is aliased to.</param>
        /// <remarks>
        /// <para>
        /// The repository specified will be aliased to the repository when created. 
        /// The repository must not already exist.
        /// </para>
        /// <para>
        /// When the repository is created it must utilize the same repository type as 
        /// the repository it is aliased to, otherwise the aliasing will fail.
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        ///     <para><paramref name="repositoryAlias" /> is <see langword="null" />.</para>
        ///     <para>-or-</para>
        ///     <para><paramref name="repositoryTarget" /> is <see langword="null" />.</para>
        /// </exception>
        public void AliasRepository(string repositoryAlias, ILoggerRepository repositoryTarget) 
        {
            if (repositoryAlias == null) 
            {
                throw new ArgumentNullException("repositoryAlias");
            }
            if (repositoryTarget == null) 
            {
                throw new ArgumentNullException("repositoryTarget");
            }

            lock(this) 
            {
                // Check if the alias is already set
                if (this.m_alias2repositoryMap.Contains(repositoryAlias)) 
                {
                    // Check if this is a duplicate of the current alias
                    if (repositoryTarget != ((ILoggerRepository)this.m_alias2repositoryMap[repositoryAlias])) 
                    {
                        // Cannot redefine existing alias
                        throw new InvalidOperationException("Repository [" + repositoryAlias + "] is already aliased to repository [" + ((ILoggerRepository)this.m_alias2repositoryMap[repositoryAlias]).Name + "]. Aliases cannot be redefined.");
                    }
                }
                    // Check if the alias is already mapped to a repository
                else if (this.m_name2repositoryMap.Contains(repositoryAlias)) 
                {
                    // Check if this is a duplicate of the current mapping
                    if ( repositoryTarget != ((ILoggerRepository)this.m_name2repositoryMap[repositoryAlias]) ) 
                    {
                        // Cannot define alias for already mapped repository
                        throw new InvalidOperationException("Repository [" + repositoryAlias + "] already exists and cannot be aliased to repository [" + repositoryTarget.Name + "].");
                    }
                }
                else 
                {
                    // Set the alias
                    this.m_alias2repositoryMap[repositoryAlias] = repositoryTarget;
                }
            }
        }

        /// <summary>
        /// Notifies the registered listeners that the repository has been created.
        /// </summary>
        /// <param name="repository">The repository that has been created.</param>
        /// <remarks>
        /// <para>
        /// Raises the <see cref="LoggerRepositoryCreatedEvent"/> event.
        /// </para>
        /// </remarks>
        protected virtual void OnLoggerRepositoryCreatedEvent(ILoggerRepository repository) 
        {
            LoggerRepositoryCreationEventHandler handler = this.m_loggerRepositoryCreatedEvent;
            if (handler != null) 
            {
                handler(this, new LoggerRepositoryCreationEventArgs(repository));
            }
        }

        /// <summary>
        /// Gets the repository name and repository type for the specified assembly.
        /// </summary>
        /// <param name="assembly">The assembly that has a <see cref="log4net.Config.RepositoryAttribute"/>.</param>
        /// <param name="repositoryName">in/out param to hold the repository name to use for the assembly, caller should set this to the default value before calling.</param>
        /// <param name="repositoryType">in/out param to hold the type of the repository to create for the assembly, caller should set this to the default value before calling.</param>
        /// <exception cref="ArgumentNullException"><paramref name="assembly" /> is <see langword="null" />.</exception>
        private void GetInfoForAssembly(Assembly assembly, ref string repositoryName, ref Type repositoryType)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }

            try
            {
                LogLog.Debug(declaringType, "Assembly [" + assembly.FullName + "] Loaded From [" + SystemInfo.AssemblyLocationInfo(assembly) + "]");
            }
            catch
            {
                // Ignore exception from debug call
            }

            try
            {
                // Look for the RepositoryAttribute on the assembly 
#if NETSTANDARD
                object[] repositoryAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.RepositoryAttribute)).ToArray();
#else
                object[] repositoryAttributes = Attribute.GetCustomAttributes(assembly, typeof(RepositoryAttribute), false);
#endif
                if (repositoryAttributes == null || repositoryAttributes.Length == 0)
                {
                    // This is not a problem, but its nice to know what is going on.
                    LogLog.Debug(declaringType, "Assembly [" + assembly + "] does not have a RepositoryAttribute specified.");
                }
                else
                {
                    if (repositoryAttributes.Length > 1)
                    {
                        LogLog.Error(declaringType, "Assembly [" + assembly + "] has multiple log4net.Config.RepositoryAttribute assembly attributes. Only using first occurrence.");
                    }

                    RepositoryAttribute domAttr = repositoryAttributes[0] as RepositoryAttribute;

                    if (domAttr == null)
                    {
                        LogLog.Error(declaringType, "Assembly [" + assembly + "] has a RepositoryAttribute but it does not!.");
                    }
                    else
                    {
                        // If the Name property is set then override the default
                        if (domAttr.Name != null)
                        {
                            repositoryName = domAttr.Name;
                        }

                        // If the RepositoryType property is set then override the default
                        if (domAttr.RepositoryType != null)
                        {
                            // Check that the type is a repository
                            if (typeof(ILoggerRepository).IsAssignableFrom(domAttr.RepositoryType))
                            {
                                repositoryType = domAttr.RepositoryType;
                            }
                            else
                            {
                                LogLog.Error(declaringType, "DefaultRepositorySelector: Repository Type [" + domAttr.RepositoryType + "] must implement the ILoggerRepository interface.");
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                LogLog.Error(declaringType, "Unhandled exception in GetInfoForAssembly", ex);
            }
        }

        /// <summary>
        /// Configures the repository using information from the assembly.
        /// </summary>
        /// <param name="assembly">The assembly containing <see cref="log4net.Config.ConfiguratorAttribute"/>
        /// attributes which define the configuration for the repository.</param>
        /// <param name="repository">The repository to configure.</param>
        /// <exception cref="ArgumentNullException">
        ///     <para><paramref name="assembly" /> is <see langword="null" />.</para>
        ///     <para>-or-</para>
        ///     <para><paramref name="repository" /> is <see langword="null" />.</para>
        /// </exception>
        private void ConfigureRepository(Assembly assembly, ILoggerRepository repository)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }
            if (repository == null)
            {
                throw new ArgumentNullException("repository");
            }

            // Look for the Configurator attributes (e.g. XmlConfiguratorAttribute) on the assembly
#if NETSTANDARD
            object[] configAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.ConfiguratorAttribute)).ToArray();
#else
            object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(ConfiguratorAttribute), false);
#endif
            if (configAttributes != null && configAttributes.Length > 0)
            {
                // Sort the ConfiguratorAttributes in priority order
                Array.Sort(configAttributes);

                // Delegate to the attribute the job of configuring the repository
                foreach(ConfiguratorAttribute configAttr in configAttributes)
                {
                    if (configAttr != null)
                    {
                        try
                        {
                            configAttr.Configure(assembly, repository);
                        }
                        catch (Exception ex)
                        {
                            LogLog.Error(declaringType, "Exception calling ["+configAttr.GetType().FullName+"] .Configure method.", ex);
                        }
                    }
                }
            }

            if (repository.Name == DefaultRepositoryName)
            {
                // Try to configure the default repository using an AppSettings specified config file
                // Do this even if the repository has been configured (or claims to be), this allows overriding
                // of the default config files etc, if that is required.

                string repositoryConfigFile = SystemInfo.GetAppSetting("log4net.Config");
                if (repositoryConfigFile != null && repositoryConfigFile.Length > 0)
                {
                    string applicationBaseDirectory = null;
                    try
                    {
                        applicationBaseDirectory = SystemInfo.ApplicationBaseDirectory;
                    }
                    catch(Exception ex)
                    {
                        LogLog.Warn(declaringType, "Exception getting ApplicationBaseDirectory. appSettings log4net.Config path ["+repositoryConfigFile+"] will be treated as an absolute URI", ex);
                    }

                    string repositoryConfigFilePath = repositoryConfigFile;
                    if (applicationBaseDirectory != null)
                    {
                        repositoryConfigFilePath = Path.Combine(applicationBaseDirectory, repositoryConfigFile);
                    }

                    // Determine whether to watch the file or not based on an app setting value:
                    bool watchRepositoryConfigFile = false;
#if NET_2_0 || MONO_2_0 || MONO_3_5 || MONO_4_0 || NETSTANDARD
                    Boolean.TryParse(SystemInfo.GetAppSetting("log4net.Config.Watch"), out watchRepositoryConfigFile);
#else
                                    {
                                        string watch = SystemInfo.GetAppSetting("log4net.Config.Watch");
                                        if (watch != null && watch.Length > 0)
                                        {
                                            try
                                            {
                                                watchRepositoryConfigFile = Boolean.Parse(watch);
                                            }
                                            catch (FormatException)
                                            {
                                                // simply not a Boolean
                                            }
                                        }
                                    }
#endif

                    if (watchRepositoryConfigFile)
                    {
                         // As we are going to watch the config file it is required to resolve it as a 
                         // physical file system path pass that in a FileInfo object to the Configurator
                        FileInfo repositoryConfigFileInfo = null;
                        try
                        {
                            repositoryConfigFileInfo = new FileInfo(repositoryConfigFilePath);
                        }
                        catch (Exception ex)
                        {
                            LogLog.Error(declaringType, "DefaultRepositorySelector: Exception while parsing log4net.Config file physical path [" + repositoryConfigFilePath + "]", ex);
                        }
                        try
                        {
                            LogLog.Debug(declaringType, "Loading and watching configuration for default repository from AppSettings specified Config path [" + repositoryConfigFilePath + "]");

                            XmlConfigurator.ConfigureAndWatch(repository, repositoryConfigFileInfo);
                        }
                        catch (Exception ex)
                        {
                            LogLog.Error(declaringType, "DefaultRepositorySelector: Exception calling XmlConfigurator.ConfigureAndWatch method with ConfigFilePath [" + repositoryConfigFilePath + "]", ex);
                        }
                    }
                    else
                    {
                    // As we are not going to watch the config file it is easiest to just resolve it as a 
                    // URI and pass that to the Configurator
                    Uri repositoryConfigUri = null;
                    try
                    {
                        repositoryConfigUri = new Uri(repositoryConfigFilePath);
                    }
                    catch(Exception ex)
                    {
                        LogLog.Error(declaringType, "Exception while parsing log4net.Config file path [" + repositoryConfigFile + "]", ex);
                    }

                    if (repositoryConfigUri != null)
                    {
                        LogLog.Debug(declaringType, "Loading configuration for default repository from AppSettings specified Config URI [" + repositoryConfigUri.ToString() + "]");

                        try
                        {
                            // TODO: Support other types of configurator
                            XmlConfigurator.Configure(repository, repositoryConfigUri);
                        }
                        catch (Exception ex)
                        {
                            LogLog.Error(declaringType, "Exception calling XmlConfigurator.Configure method with ConfigUri ["+repositoryConfigUri+"]", ex);
                        }
                    }
                    }
                }
            }
        }

        /// <summary>
        /// Loads the attribute defined plugins on the assembly.
        /// </summary>
        /// <param name="assembly">The assembly that contains the attributes.</param>
        /// <param name="repository">The repository to add the plugins to.</param>
        /// <exception cref="ArgumentNullException">
        ///     <para><paramref name="assembly" /> is <see langword="null" />.</para>
        ///     <para>-or-</para>
        ///     <para><paramref name="repository" /> is <see langword="null" />.</para>
        /// </exception>
        private void LoadPlugins(Assembly assembly, ILoggerRepository repository)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }
            if (repository == null)
            {
                throw new ArgumentNullException("repository");
            }

            // Look for the PluginAttribute on the assembly
#if NETSTANDARD
            object[] configAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.PluginAttribute)).ToArray();
#else
            object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(PluginAttribute), false);
#endif
            if (configAttributes != null && configAttributes.Length > 0)
            {
                foreach(Plugin.IPluginFactory configAttr in configAttributes)
                {
                    try
                    {
                        // Create the plugin and add it to the repository
                        repository.PluginMap.Add(configAttr.CreatePlugin());
                    }
                    catch(Exception ex)
                    {
                        LogLog.Error(declaringType, "Failed to create plugin. Attribute [" + configAttr.ToString() + "]", ex);
                    }
                }
            }
        }

        /// <summary>
        /// Loads the attribute defined aliases on the assembly.
        /// </summary>
        /// <param name="assembly">The assembly that contains the attributes.</param>
        /// <param name="repository">The repository to alias to.</param>
        /// <exception cref="ArgumentNullException">
        ///     <para><paramref name="assembly" /> is <see langword="null" />.</para>
        ///     <para>-or-</para>
        ///     <para><paramref name="repository" /> is <see langword="null" />.</para>
        /// </exception>
        private void LoadAliases(Assembly assembly, ILoggerRepository repository)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }
            if (repository == null)
            {
                throw new ArgumentNullException("repository");
            }

            // Look for the AliasRepositoryAttribute on the assembly
#if NETSTANDARD
            object[] configAttributes = assembly.GetCustomAttributes(typeof(log4net.Config.AliasRepositoryAttribute)).ToArray();
#else
            object[] configAttributes = Attribute.GetCustomAttributes(assembly, typeof(AliasRepositoryAttribute), false);
#endif
            if (configAttributes != null && configAttributes.Length > 0)
            {
                foreach(AliasRepositoryAttribute configAttr in configAttributes)
                {
                    try
                    {
                        this.AliasRepository(configAttr.Name, repository);
                    }
                    catch(Exception ex)
                    {
                        LogLog.Error(declaringType, "Failed to alias repository [" + configAttr.Name + "]", ex);
                    }
                }
            }
        }

        /// <summary>
        /// The fully qualified type of the DefaultRepositorySelector class.
        /// </summary>
        /// <remarks>
        /// Used by the internal logger to record the Type of the
        /// log message.
        /// </remarks>
        private static readonly Type declaringType = typeof(DefaultRepositorySelector);

        private const string DefaultRepositoryName = "log4net-default-repository";

        private readonly Hashtable m_name2repositoryMap = new Hashtable();
        private readonly Hashtable m_assembly2repositoryMap = new Hashtable();
        private readonly Hashtable m_alias2repositoryMap = new Hashtable();
        private readonly Type m_defaultRepositoryType;

        private event LoggerRepositoryCreationEventHandler m_loggerRepositoryCreatedEvent;
    }
}

#endif // !NETCF
